﻿namespace WinEdgeForm;

using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
using Windows.Win32.UI.Controls;
using Windows.Win32.UI.WindowsAndMessaging;

using static Windows.Win32.PInvoke;

public partial class Form1 : Form
{
    // 近期 WebView2 升级，并实现了对 AppRegion 的支持，实现了对窗口的拖动，但是如果在一个无边框窗口中使用 WebView2，并不能通过拖动窗口边缘来改变窗口大小
    // 通过自定义一个 Control 控件，使用 Region 属性挖掉窗口区域保留8个像素（使用 GetSystemMetrics 来确认的）左右的边框区域
    // 并且使用 WS_EX_TRANSPARENT 使得这个控件透明并不拦截鼠标事件
    // 处理这个 Control 的鼠标事件，使用 PostMessage 将鼠标事件转发到父窗口
    // 这样就可以实现对父窗口的缩放
    #region Resizer of borderless window
    internal class WebView2BorderlessResizer : Control
    {
        public WebView2BorderlessResizer()
        {
            Dock = DockStyle.Fill;
        }


        protected override CreateParams CreateParams
        {
            get
            {
                var cp = base.CreateParams;

                cp.ExStyle |= (int)(WINDOW_EX_STYLE.WS_EX_TRANSPARENT | WINDOW_EX_STYLE.WS_EX_TOPMOST | WINDOW_EX_STYLE.WS_EX_NOACTIVATE);

                return cp;
            }
        }

        protected override void OnResize(EventArgs eventargs)
        {
            base.OnResize(eventargs);


            var parentHandle = Parent?.Handle ?? 0;

            if (parentHandle == 0) return;

            SetWindowPos((HWND)Handle, (HWND)parentHandle, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOSIZE);

            CreateDragRegion();
        }

        private void CreateDragRegion()
        {
            var border = new Point(GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXFRAME) + GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXPADDEDBORDER), GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYFRAME) + GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXPADDEDBORDER));

            var rect = new Region(ClientRectangle);
            rect.Exclude(new Rectangle(border.X, border.Y, ClientRectangle.Width - border.X * 2, ClientRectangle.Height - border.Y * 2));

            Region = rect;

        }

        private enum HitTestNCARegionMask : byte
        {
            client = 0b0000,
            left = 0b0001,
            right = 0b0010,
            top = 0b0100,
            bottom = 0b1000,
        }

        protected override void WndProc(ref Message m)
        {
            var msg = (uint)m.Msg;

            switch (msg)
            {
                case WM_MOUSEMOVE:
                    {
                        var region = HitTestNCA(m.LParam);

                        if (region == HTNOWHERE || region == HTCLIENT) break;

                        switch (region)
                        {
                            case HTTOP:
                            case HTBOTTOM:
                                Cursor = Cursors.SizeNS;
                                break;
                            case HTLEFT:
                            case HTRIGHT:
                                Cursor = Cursors.SizeWE;
                                break;
                            case HTTOPLEFT:
                            case HTBOTTOMRIGHT:
                                Cursor = Cursors.SizeNWSE;
                                break;
                            case HTTOPRIGHT:
                            case HTBOTTOMLEFT:
                                Cursor = Cursors.SizeNESW;
                                break;
                        }
                    }
                    return;
                case WM_LBUTTONDOWN:
                    {
                        var hittest = HitTestNCA(m.LParam);

                        if (hittest == HTNOWHERE || hittest == HTCLIENT) break;

                        PostMessage((HWND)Parent!.Handle, (uint)WM_NCLBUTTONDOWN, hittest, m.LParam);
                    }
                    return;
                    //case WM_NCHITTEST:
                    //    {
                    //        m.Result = (nint)HTTRANSPARENT;
                    //    }
                    //    break;
            }

            base.WndProc(ref m);
        }

        internal uint HitTestNCA(nint lParam)
        {
            var cursor = PARAM.ToPoint(lParam);

            var border = new Point(GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXFRAME) + GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXPADDEDBORDER), GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYFRAME) + GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXPADDEDBORDER));


            if (!GetWindowRect((HWND)Parent!.Handle, out var windowRect))
            {
                return HTNOWHERE;
            }

            ClientToScreen((HWND)Handle, ref cursor);

            //var drag = _dragable ? HTCAPTION : HTCLIENT;

            var _resizable = true;

            var result =
                (byte)HitTestNCARegionMask.left * (cursor.X < (windowRect.left + border.X) ? 1 : 0) |
                (byte)HitTestNCARegionMask.right * (cursor.X >= (windowRect.right - border.X) ? 1 : 0) |
                (byte)HitTestNCARegionMask.top * (cursor.Y < (windowRect.top + border.Y) ? 1 : 0) |
                (byte)HitTestNCARegionMask.bottom * (cursor.Y >= (windowRect.bottom - border.Y) ? 1 : 0);

            return result switch
            {
                (byte)HitTestNCARegionMask.left => _resizable ? HTLEFT : HTCLIENT,
                (byte)HitTestNCARegionMask.right => _resizable ? HTRIGHT : HTCLIENT,
                (byte)HitTestNCARegionMask.top => _resizable ? HTTOP : HTCLIENT,
                (byte)HitTestNCARegionMask.bottom => _resizable ? HTBOTTOM : HTCLIENT,
                (byte)(HitTestNCARegionMask.top | HitTestNCARegionMask.left) => _resizable ? HTTOPLEFT : HTCLIENT,
                (byte)(HitTestNCARegionMask.top | HitTestNCARegionMask.right) => _resizable ? HTTOPRIGHT : HTCLIENT,
                (byte)(HitTestNCARegionMask.bottom | HitTestNCARegionMask.left) => _resizable ? HTBOTTOMLEFT : HTCLIENT,
                (byte)(HitTestNCARegionMask.bottom | HitTestNCARegionMask.right) => _resizable ? HTBOTTOMRIGHT : HTCLIENT,
                (byte)HitTestNCARegionMask.client => HTCLIENT,
                _ => HTNOWHERE,
            };
        }
        internal static class PARAM
        {
            public static nint FromLowHigh(int low, int high) => ToInt(low, high);

            public static nint FromLowHighUnsigned(int low, int high)
                // Convert the int to an uint before converting it to a pointer type,
                // which ensures the high DWORD being zero for 64-bit pointers.
                // This corresponds to the logic of the MAKELPARAM/MAKEWPARAM/MAKELRESULT
                // macros.
                => (nint)(uint)ToInt(low, high);

            public static int ToInt(int low, int high)
                => (high << 16) | (low & 0xffff);

            public static int HIWORD(int n)
                => (n >> 16) & 0xffff;

            public static int LOWORD(int n)
                => n & 0xffff;

            public static int LOWORD(nint n)
                => LOWORD((int)n);

            public static int HIWORD(nint n)
                => HIWORD((int)n);

            public static int SignedHIWORD(nint n)
                => SignedHIWORD((int)n);

            public static int SignedLOWORD(nint n)
                => SignedLOWORD(unchecked((int)n));

            public static int SignedHIWORD(int n)
                => (short)HIWORD(n);

            public static int SignedLOWORD(int n)
                => (short)LOWORD(n);

            public static nint FromBool(bool value)
                => (nint)(BOOL)(value);

            /// <summary>
            ///  Hard casts to <see langword="int" /> without bounds checks.
            /// </summary>
            public static int ToInt(nint param) => (int)param;

            /// <summary>
            ///  Hard casts to <see langword="uint" /> without bounds checks.
            /// </summary>
            public static uint ToUInt(nint param) => (uint)param;

            /// <summary>
            ///  Packs a <see cref="Point"/> into a PARAM.
            /// </summary>
            public static nint FromPoint(Point point)
                => FromLowHigh(point.X, point.Y);

            /// <summary>
            ///  Unpacks a <see cref="Point"/> from a PARAM.
            /// </summary>
            public static Point ToPoint(nint param)
                => new(SignedLOWORD(param), SignedHIWORD(param));
        }

    }

    #endregion


    CoreWebView2Controller? _controller;

    public bool IsWebViewInitialized => _controller != null;
    public CoreWebView2Controller WebViewController => _controller ?? throw new InvalidOperationException("WebView2 controller is not initialized.");
    public CoreWebView2 WebView => WebViewController.CoreWebView2;

    WebView2BorderlessResizer WindowBorderResizer = new WebView2BorderlessResizer();

    public Form1()
    {
        InitializeComponent();

        AutoScaleMode = AutoScaleMode.Dpi;
        Size = new Size(1920, 1080);

        Controls.Add(WindowBorderResizer);
    }

    protected override CreateParams CreateParams
    {
        get
        {
            var cp = base.CreateParams;

            cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_NOREDIRECTIONBITMAP;

            return cp;
        }
    }

    protected override void CreateHandle()
    {
        base.CreateHandle();

        unsafe
        {

            //启动 Win11 毛玻璃效果，Win10 不支持，但是 Win10 可以通过 WindowsApp SDK 提供的 Backdrop Brush 来自己绘制毛玻璃
            var type = DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW;
            DwmSetWindowAttribute((HWND)Handle, DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE, &type, (uint)sizeof(DWM_SYSTEMBACKDROP_TYPE));
            DwmExtendFrameIntoClientArea((HWND)Handle, new MARGINS()
            {
                cxLeftWidth = 0,
                cxRightWidth = 0,
                cyTopHeight = 1,
                cyBottomHeight = 0
            });
        }

        CreateWebView2Browser();
    }


    private async void CreateWebView2Browser()
    {
        var env = Program.WebView2Environment;

        if (env == null)
        {
            throw new InvalidOperationException("WebView2 environment is not initialized.");
        }

        var opts = env.CreateCoreWebView2ControllerOptions();

        opts.ScriptLocale = $"{Application.CurrentCulture}";

        _controller = await env.CreateCoreWebView2ControllerAsync(Handle, opts);

        _controller.DefaultBackgroundColor = Color.Transparent;

        _controller.Bounds = ClientRectangle;

        _controller.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;

        _controller.CoreWebView2.Navigate("https://cn.bing.com");
    }

    private void CoreWebView2_DOMContentLoaded(object? sender, CoreWebView2DOMContentLoadedEventArgs e)
    {
        // Make the header of https://cn.bing.com draggable
        WebView.ExecuteScriptAsync(""""
(()=>{
const headerEl = document.querySelector("#hdr");
//headerEl.querySelectorAll(":scope>div").forEach(c=>c.style.appRegion="no-drag");
headerEl.style.appRegion="drag";
})();
"""");

    }

    protected override void WndProc(ref Message m)
    {
        var msg = (uint)m.Msg;
        switch (msg)
        {
            case WM_NCHITTEST:
                break;
            case WM_MOVE:
            case WM_MOVING:
                _controller?.NotifyParentWindowPositionChanged();
                break;
            case WM_NCCALCSIZE:
                if (m.WParam == 1)
                {
                    m.Result = 0;
                    return;
                }
                break;
            case WM_ERASEBKGND:
                m.Result = 1;
                return;
        }

        base.WndProc(ref m);
    }

    protected override void OnResize(EventArgs e)
    {

        base.OnResize(e);

        if (IsWebViewInitialized)
        {
            WebViewController.Bounds = ClientRectangle;
        }

    }



}



